#include <errno.h>
#include <fcntl.h>
#include <math.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <ctype.h>

#include "base/Configuration.h"
#include "base/Simulator.h"
#include "base/simapi.h"
#include "base/threads/PthreadInterface.h"
#include "util/Logger.h"
#include "CPU.h"

Logger* Log = NULL;
Logger* nullLog = NULL;

int sim_sprintf (char* buffer, const char* text, ...) {
    va_list ap;
    va_start(ap,text);
    int count = sim_vsnprintf(buffer, 4096, text, ap);
    va_end(ap);
    return count;
}

int sim_snprintf (char* buffer, size_t size, const char* text, ...) {
    va_list ap;
    va_start(ap,text);
    int count = sim_vsnprintf(buffer, size, text, ap);
    va_end(ap);
    return count;
}


int sim_atoi (const char* buf, const char** newbuf) {
    int num = 0;

    for (; *buf && isdigit(*buf); ++buf) {
        num *= 10;
        num += *buf - '0';
    }
    *newbuf = buf;
    return num;
}

/* 
   int sim_vsnprintf(buffer, size, text, ap)
   
   Like the real vsnprintf, but slower, and less featureful...
   The #if defined(HOST_64) stuff essentially turns an %ll into a %l if
   HOST_64 is set.  Which is what the old SubstituteLLx used to do.
   This mimics old behavior, but is untested.
*/
static char hexTable[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
                           'a', 'b', 'c', 'd', 'e', 'f' };


#define OUT(c) { *out = c; out++; size++; if (size > max) { ASSERT(false); } }
#ifdef PPC
#define VA_LIST_REF_T va_list
#define VA_LIST_REF(x) x
#define VA_LIST(x) x
#else
#define VA_LIST_REF_T va_list*
#define VA_LIST_REF(x) &x
#define VA_LIST(x) *x
#endif
int _sim_vsnprintf (char*         buffer,
                    int           max,
                    const char*   text,
                    VA_LIST_REF_T ap)
{
    const char* p;
    char* out = buffer;
    int size = 0;
    char tmpBuf[4096];
    int  tmpBufIndex;
    int i;

    /*  Values  */
    int                ival = 0;
    // float           fval = 0.0;
    unsigned int       uival = 0;
    long long          llival = 0;
    unsigned long long lluival = 0;
    char*              sval = 0;
    char               cval = 0;

    /*  Some booleans for state  */
    struct _flags {
        int bUnsigned;
        int bCapital;
        int iLs;
        int bDouble;
        int iOutputLength;
        bool bZeroPadding;
        bool bLeftAlign;
    } flags;

    for (p = text; *p; p++) {
        if (*p != '%') {
            OUT(*p);
            continue;
        }

        memset(&flags, 0, sizeof(struct _flags));

        ++p;
        /* take care of formatting... */
        for (; *p; ++p) {
            if (*p == '-') {
                flags.bLeftAlign = true;
                continue;
            }

            /* deal with output size */
            if (isdigit(*p)) {
                const char* newp;

                if (*p == '0') {
                    flags.bZeroPadding = true;
                }

                flags.iOutputLength = sim_atoi(p, &newp);
                p = newp;
            }
            if (*p == '.') {
                continue;
            }
            if (*p == 'l') {
                flags.iLs++;
                continue;
            }
            break;
        }
        switch (*p) {
            case 'c': {
                cval = va_arg(VA_LIST(ap), int);
                OUT(cval);
                break;
            }
            case 'u': {
                flags.bUnsigned = 1;
            }
            case 'd':
            case 'i': {
#if defined(HOST_64)
                ival = va_arg(VA_LIST(ap), int);
#else
                if (flags.iLs == 2) {
                    llival = va_arg(VA_LIST(ap), long long);
                } else {
                    ival = va_arg(VA_LIST(ap), int);
                }
#endif
                if (flags.iOutputLength > 0) {
                    int length = 1;
                    if (flags.iLs == 2) {
                        if (llival >= 10) {
                            length += (int)floor(log10((double)llival));
                        }
                    } else {
                        if (ival >= 10) {
                            length += (int)floor(log10((double)ival));
                        }
                    }
                    for (i = 0; i < flags.iOutputLength - length; i++) {
                        if (flags.bZeroPadding) {
                            OUT('0');
                        } else {
                            OUT(' ');
                        }
                    }
                }

                tmpBufIndex = 0;
                if (flags.iLs == 2) {
                    if (flags.bUnsigned) {
                        for (lluival = (unsigned long long)llival;
                             lluival > 0;
                             lluival /= 10)
                        {
                            tmpBuf[tmpBufIndex++] = (lluival % 10) + '0';
                        }
                    } else {
                        if (llival < 0) {
                            OUT('-');
                            llival *= -1;
                        }
                        for (;
                             llival > 0;
                             llival /= 10)
                        {
                            tmpBuf[tmpBufIndex++] = (llival % 10) + '0';
                        }
                    }
                } else {
                    if (flags.bUnsigned) {
                        for (uival = (unsigned int)ival;
                             uival > 0;
                             uival /= 10)
                        {
                            tmpBuf[tmpBufIndex++] = (uival % 10) + '0';
                        }
                    } else {
                        if (ival == (int)0x80000000) {
                            // below logic does not work for
                            // 0x80000000 since negating it leaves a
                            // negative number. simple print -2147483648
                            OUT('-');
                            OUT('2');
                            OUT('1');
                            OUT('4');
                            OUT('7');
                            OUT('4');
                            OUT('8');
                            OUT('3');
                            OUT('6');
                            OUT('4');
                            OUT('8');
                        }
                        else {
                            if (ival < 0) {
                                OUT('-');
                                ival *= -1;
                            }
                            for (; ival > 0; ival /= 10) {
                                tmpBuf[tmpBufIndex++] = (ival % 10) + '0';
                            }
                        }
                    }
                }
                if (tmpBufIndex == 0) {
                    tmpBuf[tmpBufIndex++] = '0';
                }
                for (tmpBufIndex--; tmpBufIndex >= 0; --tmpBufIndex) {
                    OUT(tmpBuf[tmpBufIndex]);
                }
                break;
            }
            case 's': {
                int count;
                sval = va_arg(VA_LIST(ap), char*);

                if (flags.iOutputLength > 0 && !flags.bLeftAlign) {
                    int length = strlen(sval);
                    for (i = 0; i < flags.iOutputLength - length; ++i) {
                        OUT(' ');
                    }
                }

                // note that we do not truncate strings longer than
                // iOutputLength, old versions did
                for (count = 0; *sval; sval++)
                {
                    OUT(*sval);
                    count++;
                }
                if (flags.bLeftAlign) {
                    for (i = flags.iOutputLength - count; i > 0; i--) {
                        OUT(' ');
                    }
                }
                break;
            }
            case 'a':
            case 'e':
            case 'f':
            case 'g': {
                /* I think all floats get passed as doubles */
                flags.iLs = 2;
                flags.bDouble = 1;
            }
            case 'p':
            case 'X':
            case 'x': {
                if (*p == 'p') {
                    OUT('0');
                    OUT('x');
                }
                if (*p == 'X') {
                    flags.bCapital = 1;
                }
#if defined(HOST_64)
                lluival = va_arg(VA_LIST(ap), unsigned int);
                for (i = 7; i >= 0; --i) {
                    char c = hexTable[(lluival >> 4*i) & 0xf];
                    if (flags.bCapital && c >= 'a') {
                        c -= 'a' - 'A';
                    }
                    OUT(c);
                }
#else
                if (flags.iLs == 2) {
                    if (flags.bDouble) {
                        double d = va_arg(VA_LIST(ap), double);
                        lluival = double2longlong(d);
                    }
                    else {
                        lluival = va_arg(VA_LIST(ap), unsigned long long);
                    }
                }
                else {
                    lluival = va_arg(VA_LIST(ap), unsigned int);
                }
                int outputTotal = (32*(flags.iLs == 2 ? 2 : 1))/4;
                int outputMax   = (flags.iOutputLength ?
                                   flags.iOutputLength :
                                   outputTotal);
                for (i = outputMax-1; i >= 0; --i) {
                    char c = hexTable[(lluival >> 4*i) & 0xf];
                    if (flags.bCapital && c >= 'a') {
                        c -= 'a' - 'A';
                    }
                    OUT(c);
                }
#endif
                break;
            }
            case '%': {
                OUT('%');
                break;
            }
            default: {
                OUT(*p);
            }
        }
    }
    *out = '\0';
    return size;
}

int sim_vsnprintf (char* buffer,
                   int max,
                   const char* text,
                   va_list ap)
{
    return _sim_vsnprintf(buffer, max, text, VA_LIST_REF(ap));
}

int sim_vsprintf (char* buffer, const char* text, va_list ap) 
{
    return sim_vsnprintf(buffer, 4096, text, ap);
}


NEW_DELETE_OPERATORS_CPP(Logger,1000);
NEW_DELETE_OPERATORS_TEMPLATE_CPP(__quote__(List<Logger::prefixCB*>), 1);

static const char sev_chars[] = "diwe";

Entity* Logger::curEntity = NULL;

Logger::open_file Logger::gbl_open_files[MAX_FILES];
int Logger::gbl_num_open_files = -1;

bool Logger::log_debug = false;
bool Logger::loopOnError = false;
char Logger::output_path[MAX_PATH]; // static so I don't have to deal with figuring out when to free
#ifdef __linux__
char Logger::proc_pid_maps_path[MAX_PATH];
#endif

char Logger::buffer[PRINTBUFFER_SIZE];
char Logger::formatBuffer[PRINTBUFFER_SIZE];

bool Logger::initializedClass = false;
void Logger::initializeClass () {
    if (initializedClass) {
        return;
    }
    initializedClass = true;
    
    log_debug = global_cfg->getBooleanArgument("log_debug", "Debug the logging system");
    loopOnError = global_cfg->getBooleanArgument("loopOnError", "Hang on error to allow gdb to attach to debug state"); 
    const char* cfg_output_path = global_cfg->getStringArgument("output_path",
                                                                "relative output directory (no spaces)",
                                                                ".");

    // make our own copy so we have it even when the Configuration class goes away.
    if (strlen(cfg_output_path) >= (int)MAX_PATH) {
        sim_snprintf(buffer, PRINTBUFFER_SIZE, "Path must be less than %d characters, sorry.\n", MAX_PATH);
        writeOrDie(2, buffer);
        exit(1);
    }

    strncpy(output_path, cfg_output_path, MAX_PATH);

#ifdef __linux__
    // we allocate this now so we do not need to need to do anything
    // when we are crashing
    sim_snprintf(proc_pid_maps_path, MAX_PATH, "/proc/%d/maps", getpid());
#endif
}

void Logger::init_openfiles () {
    strcpy(gbl_open_files[0].filename, "-");
    gbl_open_files[0].fd = 1;
    gbl_open_files[0].count = 0;

    strcpy(gbl_open_files[1].filename, "stdout");
    gbl_open_files[1].fd = 1;
    gbl_open_files[1].count = 0;

    strcpy(gbl_open_files[2].filename, "stderr");
    gbl_open_files[2].fd = 2;
    gbl_open_files[2].count = 0;

    gbl_num_open_files = 3;
}

void Logger::getOutputPath (char* buffer, const char* filename) {
    strcpy(buffer, output_path);
    strcat(buffer, "/");
    strcat(buffer, filename);
}

Logger::Logger (const char* _name, 
                const char* _cat, 
                const char* default_dest, 
                const char* desc, 
                Entity* _entity,
                bool doDefaultPrefix,
                bool configurable) :
    name(_name),
    entity(_entity),
    configurable(configurable),
    doPrefix(true)
{
    // save the category
    ASSERT(_cat != NULL);
    cat = (char*)CALLOC(strlen(_cat)+1,sizeof(char));
    strcpy(cat, _cat);

    const char* default_log = "log.sim";
    const char* cfg_desc = desc ? desc : name;

    for (int i=0; i<NUM_SEVS; i++) {
        num_dests[i] = 0;
    }

    // get destination file names and open them if necessary
    if (gbl_num_open_files < 0) {
        init_openfiles();
    }
    const char* cfg_value;
    if (configurable) {
        cfg_value = global_cfg->getStringArgument(name, cfg_desc, default_dest);
        initializeClass();
    }
    else {
        cfg_value = default_dest;
    }
    
    char override_sev = '\0';
    if (cfg_value[0] == ':') {
        override_sev = cfg_value[1];
        if (strcmp(default_dest, "none")) {
            cfg_value = default_log;
        } else {
            cfg_value = default_dest;
        }
    }

    if (strcmp(cfg_value, "none")) {
        char filename[1024];
        char sev_flag = override_sev;
        bool used = false;
        const char* t1;
        char* t2;
        for (t1 = cfg_value, t2 = filename; *t1; t1++) {
            switch (*t1) {
            case ',':
                if (!used) {
                    *t2 = '\0';
                    t2 = filename; 
                    openFile(filename, override_sev);
                    used = true;
                }
                break;
            case ':':
                *t2 = '\0';
                t2 = filename;
                if (override_sev == '\0') {
                    sev_flag = *++t1;
                } else {
                    t1++; // skip given flag
                }
                if (!sev_flag || ((*(t1+1) != ',') && (*(t1+1) != '\0'))) {
                    sim_snprintf(buffer, PRINTBUFFER_SIZE, "Syntax error in log destination list: %s.\n", cfg_value);
                    writeOrDie(2, buffer);
                    bt_exit(1);
                }
                openFile(filename, sev_flag);
                used = true;
                break;
            default:
                used = false;
                *t2++ = *t1;
                continue;
            }
        }
        if (!used) {
            *t2 = '\0';
            openFile(filename, override_sev);
        }
    }

    if (doDefaultPrefix) {
        addPrefixCB(&defaultPrefix, this);
    }
}

Logger::~Logger () {
    for (int i=0; i<num_open_files; i++) {
        if (--open_files[i]->count == 0 && open_files[i]->fd > 2) {
            if (log_debug) {
                sim_snprintf(buffer, PRINTBUFFER_SIZE, "Closing %s.\n", open_files[i]->filename);
                writeOrDie(1, buffer);
            }
            closeOrDie(open_files[i]->fd);
            open_files[i]->fd = -1;
        } else if (log_debug) {
            sim_snprintf(buffer, PRINTBUFFER_SIZE, "Not closing %s: %d.\n", open_files[i]->filename, open_files[i]->count);
            writeOrDie(1, buffer);
        }
    }
    List<prefixCB*>::iterator i;
    for (i=prefixs.begin(); i != prefixs.end(); i++) {
        FREE(*i);
    }
    FREE(cat);
}

void Logger::debugHex (uint const* data, uint const len) {
    outputHex(SEV_DEBUG, data, len);
}


#define DEF_PRINT_FUNC(name, sev)                              \
    void Logger::name (const char* text, ...) {                \
        va_list ap;                                            \
        va_start(ap, text);                                    \
        _output(sev, text, ap);                                \
        va_end(ap);                                            \
    }                                                          \
    void Logger::name (Entity* e, const char* text, ...) {     \
        va_list ap;                                            \
        va_start(ap, text);                                    \
        _output(sev, text, ap, e);                             \
        va_end(ap);                                            \
    } 

#define DEF_PRINT_FUNCS(name, letter, sev)      \
    DEF_PRINT_FUNC(name, sev);                  \
    DEF_PRINT_FUNC(letter, sev);

DEF_PRINT_FUNCS(debug,   d, SEV_DEBUG);
DEF_PRINT_FUNCS(info,    i, SEV_INFO);
DEF_PRINT_FUNCS(warning, w, SEV_WARNING);

void Logger::error (const char* text, ...) {
    va_list ap;
    va_start(ap, text);
    _output(SEV_ERROR, text, ap);
    bt_exit(1);
    va_end(ap);
}

void Logger::error (Entity* e, const char* text, ...) {
    va_list ap;
    va_start(ap, text);
    _output(SEV_ERROR, text, ap, e);
    bt_exit(1);
    va_end(ap);
}

void Logger::e (const char* text, ...) {
    va_list ap;
    va_start(ap, text);
    _output(SEV_ERROR, text, ap);
    bt_exit(1);
    va_end(ap);
}

void Logger::e (Entity* e, const char* text, ...) {
    va_list ap;
    va_start(ap, text);
    _output(SEV_ERROR, text, ap, e);
    bt_exit(1);
    va_end(ap);
}

void Logger::output (SEVERITY sev, const char* text, ...) {
    va_list ap;
    va_start(ap, text);
    _output(sev, text, ap);
    if (sev == SEV_ERROR) {
        bt_exit(1);
    }
    va_end(ap);
}

void Logger::output (Entity* e, SEVERITY sev, const char* text, ...) {
    va_list ap;
    va_start(ap, text);
    _output(sev, text, ap, e);
    if (sev == SEV_ERROR) {
        bt_exit(1);
    }
    va_end(ap);
}

void Logger::o (SEVERITY sev, const char* text, ...) {
    va_list ap;
    va_start(ap, text);
    _output(sev, text, ap);
    if (sev == SEV_ERROR) {
        bt_exit(1);
    }
    va_end(ap);
}

void Logger::o (Entity* e, SEVERITY sev, const char* text, ...) {
    va_list ap;
    va_start(ap, text);
    _output(sev, text, ap, e);
    if (sev == SEV_ERROR) {
        bt_exit(1);
    }
    va_end(ap);
}

void Logger::outputHex (SEVERITY sev, uint const* const data, uint const len) {
    if (num_dests[sev] == 0 || len == 0) {
        return;
    }

    output(sev, "%08x", data[0]);
    for (uint i = 1; i < len; ++i) {
        output(sev, " %08x", data[i]);
    }
    output(sev, "\n");
}


// private functions
__inline__
int Logger::bufferLeft (char* index) {
    return PRINTBUFFER_SIZE-(index-buffer);
}

char* Logger::defaultPrefix (char* index, Entity* e, void* data) {
    index += sim_sprintf(index, "%s:", e->getName());
    index += sim_sprintf(index, "%lld:", global_sim->getSimclock());
    Logger* l = static_cast<Logger*>(data);
    if (strlen(l->cat)) {
        index += sim_sprintf(index, "%s|", l->cat);
    }    
    return index;
}

void Logger::addPrefixCB (char* (*func)(char*, Entity*, void*), void* data) {
    prefixCB* cb = (prefixCB*)CALLOC(1, sizeof(prefixCB));
    cb->func = func;
    cb->data = data;
    prefixs.add(cb);
}

// If output function has an entity, we use its name,
// next we check for a Logger specific entity specified
// at Logger creation, finally we check curEntity,
// if none of those are set, or if the pointer = -1
// (yeah, ugly, but works) do not print prefix
// good for printing environment before we even start, 
// when you want a "special prefix" etc.

char* Logger::putPrefix (char* index, Entity* e) {
    if (e == NULL) {
        e = entity ? entity : curEntity;
    }
    if (!e || (int)e == -1) { 
        return index;
    }
    List<prefixCB*>::iterator i;
    for (i=prefixs.begin(); i != prefixs.end(); i++) {
        index = ((*i)->func)(index, e, ((*i)->data));
    }
    
    return index;
}    

void Logger::_output (SEVERITY sev, const char* text, va_list ap, Entity* e) {
    if (num_dests[sev] == 0) {
        return;
    }
    char* c = buffer;
    if (doPrefix) {
        c = putPrefix(c, e);
    }
    if (sev == SEV_ERROR) {
        c += sim_sprintf(c, "\nERROR pid=%d: ", getpid());
    }            
    // make a copy so we can play with it without segfaulting
    strncpy(formatBuffer, text, PRINTBUFFER_SIZE);
    char* format = formatBuffer;
    char* last_char = format + strlen(format) - 1;
    char* newl = index(format, '\n');
    while (newl != NULL && newl != last_char) { 
        *newl = '\0';
#ifdef PPC
        c += sim_vsnprintf(c, bufferLeft(c), format, ap);
#else
        c += _sim_vsnprintf(c, bufferLeft(c), format, &ap);
#endif
        *c++ = '\n';
        c = putPrefix(c, e);
        format = newl+1;
        newl = index(format, '\n');
    }
    doPrefix = (newl == last_char); // save this info for next time
    c += sim_vsnprintf(c, bufferLeft(c), format, ap);
    for (int i=0; i<num_dests[sev]; i++) {
        if (dest[sev][i]->wrapsize != 0) {
            dest[sev][i]->written += (int)(c-buffer);
            if (dest[sev][i]->written > dest[sev][i]->wrapsize) {
                closeOrDie(dest[sev][i]->fd);
                rename(dest[sev][i]->filepath, dest[sev][i]->wrappath);
                // don't test for rename error, since it causes "make test -j" to fail.
                dest[sev][i]->fd = openOrDie(dest[sev][i]->filepath);
                dest[sev][i]->written = 0;
            }
        }
        writeOrDie(dest[sev][i]->fd, buffer);
    }
}


void Logger::assignDest (int low_sev, open_file* d) {
    for (int i=low_sev; i<NUM_SEVS; i++) {
        dest[i][num_dests[i]++] = d;
    }
}

void Logger::newSeverity (int low_sev) {
    bool found=false;
    for (int i=0; i<low_sev; i++) {
        for (int j=0; j<num_dests[i]; j++) {
            dest[i][j] = NULL;
        }
        num_dests[i] = 0;
    }
    for (int f=0; f<num_open_files; f++) {
        for (int i=low_sev; i<NUM_SEVS; i++) {
            for (int j=0; j<num_dests[i] && !found; j++) {
                if (dest[i][j] == open_files[f]) {
                    found = true;
                }
            }
            if (!found) {
                dest[i][num_dests[i]++] = open_files[f];
            }
            found = false;
        }
    }
}

void Logger::openFile (const char* filename, char sev) {
    int low_sev = 1;       // default severity of info
    bool sev_found = !sev; // if sev is the default '\0', consider it found
    for (int i=0; i<NUM_SEVS; i++) {
        if (sev == sev_chars[i]) {
            low_sev = i;
            sev_found = true;
        }
    }
    if (!sev_found) {
        sim_snprintf(buffer, PRINTBUFFER_SIZE, "Invalid severity flag for %s: %c\n", filename, sev);
        writeOrDie(2, buffer);
        bt_exit(1);
    }
    
    for (int i=low_sev; i<NUM_SEVS; i++) {
        if (num_dests[i] == MAX_DESTS) {
            sim_snprintf(buffer, PRINTBUFFER_SIZE, "Too many destinations for %s\n", filename);
            writeOrDie(2, buffer);
            bt_exit(1);
        }
    }
    
    for (int i=0; i<gbl_num_open_files; i++) {
        if (!strcmp(filename, gbl_open_files[i].filename)) {
            gbl_open_files[i].count++;
            open_files[num_open_files++] = &gbl_open_files[i];
            if (log_debug) {
                sim_snprintf(buffer, PRINTBUFFER_SIZE, "Reusing %s: %d.\n", filename, gbl_open_files[i].count);
                writeOrDie(1, buffer);
            }
            assignDest(low_sev, &gbl_open_files[i]);
            return;
        }
    }
    
    char filepath[2048];
    Logger::getOutputPath(filepath, filename);

    if (log_debug) {
        sim_snprintf(buffer, PRINTBUFFER_SIZE, "Opening %s... ", filepath);
        writeOrDie(1, buffer);
    }
    
    int fd = openOrDie(filepath);

    if (log_debug) {
        sim_snprintf(buffer, PRINTBUFFER_SIZE, "fd: %d\n", fd);
        writeOrDie(1, buffer);
    }

    open_file* new_file = &gbl_open_files[gbl_num_open_files++];
    strncpy(new_file->filename, filename, FILENAME_SIZE);
    strncpy(new_file->filepath, filepath, FILENAME_SIZE);
    strncpy(new_file->wrappath, filepath, FILENAME_SIZE);
    strncat(new_file->wrappath, ".old",   FILENAME_SIZE);
    new_file->count = 1;
    new_file->fd = fd;
    if (configurable && fd > 2) {
        char desc[256];
        sim_snprintf(buffer, PRINTBUFFER_SIZE, "%s_wrapsize", filename);
        sim_snprintf(desc, 256, "wrap limit for %s", filename);
        new_file->wrapsize = global_cfg->getIntegerArgument(buffer,
                                                            desc,
                                                            0,
                                                            CONFIG_UNSIGNED);
    } else {
        new_file->wrapsize = 0;
    }
    new_file->written = 0;
    open_files[num_open_files++] = new_file;
    assignDest(low_sev, new_file);
}
 
int Logger::openOrDie (const char* filename) {
    int fd = open(filename, O_WRONLY|O_CREAT|O_TRUNC, 0666);
    if (fd == -1) {
        sim_snprintf(buffer, PRINTBUFFER_SIZE, "openOrDie: Unable to open '%s'", filename);
        perror(buffer);
        bt_exit(1);
    }
    return fd;
}

void Logger::writeOrDie (int fd, char* buffer) {
    int nbytes = strlen(buffer);
    while (nbytes != 0) {
        int result = write(fd, buffer, nbytes);
        if (result == -1) {
            perror("writeOrDie");
            exit(1);
        }
        buffer += result;
        nbytes -= result;
    }
}

void Logger::closeOrDie (int fd) {
    int result = close(fd);
    if (result == 0) {
        return;
    }
    perror("closeOrDie");
    bt_exit(1);
}

#ifdef __linux__
#include <execinfo.h>
void Logger::printStackTrace (SEVERITY sev) {
    const int size = 1024;
    void* buffer[size];

    // we need to reset to the real mode PthreadInterface since
    // backtrace on cyclades running CentOS calls pthread_mutex_lock
    // and we don't want to call our version with ISA extenstions
    PthreadInterface* pthreadInterface = PthreadInterface::reset();
    int result = backtrace(buffer, size);
    PthreadInterface::setPthreadInterface(pthreadInterface);

    ASSERT(0 <= result && result <= size);
    if (num_dests[sev] != 0) {
        for (int i=0; i<num_dests[sev]; i++) {
            backtrace_symbols_fd(buffer, result, dest[sev][i]->fd);
        }
    } else {
        backtrace_symbols_fd(buffer, result, 2);
    }
}

void Logger::memory_map_fd (int out) {
    const int count = 8192;
    char* buf[count];

    // do not use openOrDie, since it will call bt_exit, which will
    // call memory_map_fd...
    int in = open(Logger::proc_pid_maps_path, O_RDONLY);
    while (true) {
        int inbytes = read(in, buf, count);
        if (inbytes == -1) {
            perror("memory_map_fd read");
            exit(1);
        }
        if (inbytes == 0) {
            break;
        }
        int outbytes = write(out, buf, inbytes);
        if (outbytes == -1) {
            perror("memory_map_fd write");
            exit(1);
        }
        ASSERT(inbytes == outbytes);
    }
    Logger::closeOrDie(in);
}

void Logger::printMemoryMap (SEVERITY sev) {
    if (num_dests[sev] != 0) {
        for (int i=0; i<num_dests[sev]; i++) {
            memory_map_fd(dest[sev][i]->fd);
        }
    } else {
        memory_map_fd(2);
    }
}
#else // !__linux__
void Logger::printStackTrace (SEVERITY sev) { }
void Logger::printMemoryMap (SEVERITY sev) { }
#endif // !__linux__

void Logger::bt_exit (int code) {
    if (global_sim != NULL) {
        warning("Current simclock is %lld at exit\n", global_sim->getSimclock());
    }
    if (curPE != NULL) {
        warning("Current processor is %d at exit\n", curPE->P->getID());
        warning("Current PC is 0x%llx at exit\n", curPE->getPC());
    }
    if (code != 0) {
        // setting inSimulation to false makes sure we run pthread_*
        // functions without ISA extensions.
        bool temp = inSimulation;
        inSimulation = false;
        printStackTrace(SEV_ERROR);
        inSimulation = temp;
        printMemoryMap(SEV_ERROR);

        if (loopOnError) {
            warning("simulator waiting at exit on error: run 'gdb --pid %d' to attach\n",
                    getpid());
            while (true) {
                sleep(1);
            }
        }
    }
    exit(code);
}

void Logger::setCurEntity (Entity* e) {
    curEntity = e;
}

